/**********************************************************************************
// Sound Source
// 
// Creation:	22 Oct 2007
// Updated:		07 Abr 2011
// Compiler:	Visual Studio 2010
//
// Notes:		Provide sound playback in games
//
**********************************************************************************/

#include "Sound.h"

/**********************************************************************************/

Sound::Sound()
{
	lpDS = NULL;		// directsound interface pointer
	numSoundFX = 0;		// initially the array is empty
}

/**********************************************************************************/

Sound::~Sound()
{
	// now release all sound buffers
	for (int i=0; i<MAXSOUNDS; i++)
	{
		if (soundFX[i].buffer)
		{
			soundFX[i].buffer->Stop();
			soundFX[i].buffer->Release();
		}
	}

	// release directsound interface
	if (lpDS)
		lpDS->Release();
}

/**********************************************************************************/

bool Sound::Initialize()
{
	// track the first time the function is entered
	static bool firstTime = true;	

	// test for very first time
	if (firstTime)
	{		
		// clear array
		ZeroMemory(soundFX, sizeof(PCMSound) * MAXSOUNDS);
			
		// reset first time
		firstTime = false;

		// create a directsound object
		if (FAILED(DirectSoundCreate(NULL, &lpDS, NULL)))
			return false;

		// set cooperation level
		if (FAILED(lpDS->SetCooperativeLevel(WindowsGame::GetWindowHandle(), DSSCL_NORMAL)))
			return false;
	} 
	else
	{
		// re-initialize the sound fx array
		for (int i=0; i<MAXSOUNDS; ++i)
		{
			// test if this sound has been loaded
			if (soundFX[i].buffer)
			{
				// stop the sound
				soundFX[i].buffer->Stop();

				// release the buffer
				soundFX[i].buffer->Release();
			}
		}

		// clear array
		ZeroMemory(soundFX, sizeof(PCMSound) * MAXSOUNDS);
	}

	// sucessful initialization
	return true;
}

/**********************************************************************************/

int Sound::Load(char * filename)
{
	// this function loads a .wav file, sets up the directsound 
	// buffer and loads the data into memory, the function returns 
	// the id number of the sound

	DSBUFFERDESC	dsBD;       // directsound description
	WAVEFORMATEX	pcmWF;      // PCM waveformat structure

	HMMIO 			hwav;		// handle to wave file
	MMCKINFO		parent;		// parent chunk
	MMCKINFO        child;		// child chunk
	WAVEFORMATEX    wfmtx;		// wave format structure

	int	soundId = numSoundFX;	// id of sound to be loaded

	UCHAR *sndBuffer;			// temporary sound buffer to hold voc data
    UCHAR *audioPtr1 = NULL;	// data ptr to first write buffer 
	UCHAR *audioPtr2 = NULL;	// data ptr to second write buffer

	DWORD audioLength1 = 0;		// length of first write buffer
	DWORD audioLength2 = 0;		// length of second write buffer
			
	// step one: find the next unused id
	if (soundId < MAXSOUNDS)
	{
		// increase sounds counter
		numSoundFX++;
	}
	else
	{
		// maximum number of sounds exceeded
		return -1;
	}

	// set up chunk info structure
	parent.ckid 	    = (FOURCC)0;
	parent.cksize 	    = 0;
	parent.fccType	    = (FOURCC)0;
	parent.dwDataOffset = 0;
	parent.dwFlags		= 0;

	// copy data
	child = parent;

	// open the WAV file
	if ((hwav = mmioOpen(filename, NULL, MMIO_READ | MMIO_ALLOCBUF))==NULL)
		return -1;

	// descend into the RIFF 
	parent.fccType = mmioFOURCC('W', 'A', 'V', 'E');

	if (mmioDescend(hwav, &parent, NULL, MMIO_FINDRIFF))
    {
		// close the file
		mmioClose(hwav, 0);

		// return error, no wave section
		return -1; 	
    }

	// descend to the WAVEfmt 
	child.ckid = mmioFOURCC('f', 'm', 't', ' ');

	if (mmioDescend(hwav, &child, &parent, 0))
    {
		// close the file
		mmioClose(hwav, 0);

		// return error, no format section
		return -1; 	
    }

	// now read the wave format information from file
	if (mmioRead(hwav, (char *)&wfmtx, sizeof(wfmtx)) != sizeof(wfmtx))
    {
		// close file
		mmioClose(hwav, 0);

		// return error, no wave format data
		return -1;
    }

	// make sure that the data format is PCM
	if (wfmtx.wFormatTag != WAVE_FORMAT_PCM)
    {
		// close the file
		mmioClose(hwav, 0);

		// return error, not the right data format
		return -1; 
    }

	// now ascend up one level, so we can access data chunk
	if (mmioAscend(hwav, &child, 0))
	{
		// close file
		mmioClose(hwav, 0);

		// return error, couldn't ascend
		return -1;
   }

	// descend to the data chunk 
	child.ckid = mmioFOURCC('d', 'a', 't', 'a');

	if (mmioDescend(hwav, &child, &parent, MMIO_FINDCHUNK))
    {
		// close file
		mmioClose(hwav, 0);

		// return error, no data
		return -1;
    }

	// finally!!!! now all we have to do is read the data in and
	// set up the directsound buffer

	// allocate the memory to load sound data
	sndBuffer = (UCHAR *) malloc(child.cksize);

	// read the wave data 
	mmioRead(hwav, (char *)sndBuffer, child.cksize);

	// close the file
	mmioClose(hwav, 0);

	// set rate and size in data structure
	soundFX[soundId].rate  = wfmtx.nSamplesPerSec;
	soundFX[soundId].size  = child.cksize;
	soundFX[soundId].state = SoundLoaded;

	// set up the format data structure
	ZeroMemory(&pcmWF, sizeof(WAVEFORMATEX));

	pcmWF.wFormatTag	  = WAVE_FORMAT_PCM;  // pulse code modulation
	pcmWF.nChannels		  = 1;                // mono 
	pcmWF.nSamplesPerSec  = 11025;            // always this rate
	pcmWF.nBlockAlign	  = 1;                
	pcmWF.nAvgBytesPerSec = pcmWF.nSamplesPerSec * pcmWF.nBlockAlign;
	pcmWF.wBitsPerSample  = 8;
	pcmWF.cbSize		  = 0;

	// prepare to create sounds buffer
	ZeroMemory(&dsBD, sizeof(DSBUFFERDESC));

	dsBD.dwSize			= sizeof(DSBUFFERDESC);
	dsBD.dwFlags		= DSBCAPS_CTRLFREQUENCY | 
		                  DSBCAPS_CTRLPAN | 
						  DSBCAPS_CTRLVOLUME | 
		                  DSBCAPS_STATIC | 
						  DSBCAPS_LOCSOFTWARE;
	dsBD.dwBufferBytes	= child.cksize;
	dsBD.lpwfxFormat	= &pcmWF;

	// create the sound buffer
	if (FAILED(lpDS->CreateSoundBuffer(&dsBD ,&soundFX[soundId].buffer, NULL)))
	{
		// release memory
		free(sndBuffer);

		// return error
		return -1;
	}

	// lock the buffer
	if (FAILED(soundFX[soundId].buffer->Lock(0, child.cksize,			
								             (void **) &audioPtr1, &audioLength1,
								             (void **) &audioPtr2, &audioLength2,
								             DSBLOCK_FROMWRITECURSOR)))
		return -1;
	
	// copy data into sound buffer
	
	// copy first section of circular buffer
	memcpy(audioPtr1, sndBuffer, audioLength1);

	// copy last section of circular buffer
	memcpy(audioPtr2, (sndBuffer+audioLength1), audioLength2);

	// unlock the buffer
	if (FAILED(soundFX[soundId].buffer->Unlock(audioPtr1, audioLength1,
									    audioPtr2, audioLength2)))
 		return -1;

	// release the temp buffer
	free(sndBuffer);

	// return id
	return soundId;
}

/**********************************************************************************/

bool Sound::Play(int id, int flags)
{
	// plays the sound at position id
	// the flag can be 0 to play once or DSBPLAY_LOOPING

	if (soundFX[id].buffer)
	{
		// reset position to start
		if (FAILED(soundFX[id].buffer->SetCurrentPosition(0)))
			return false;
	
		// play sound
		if (FAILED(soundFX[id].buffer->Play(0,0,flags)))
			return false;
	}

	// return success
	return true;
}


/**********************************************************************************/

bool Sound::Stop(int id)
{
	// stops a sound from playing
	if (soundFX[id].buffer)
	{
		soundFX[id].buffer->Stop();
		soundFX[id].buffer->SetCurrentPosition(0);
	}

	// sucessful stop
	return true;
}

/**********************************************************************************/

bool Sound::Volume(int id, int vol)
{
	// this function sets the volume on a sound 0-100

	if (FAILED(soundFX[id].buffer->SetVolume(-1*(10000-vol))))
		return false;

	// sucessful volume change
	return true;
}

/**********************************************************************************/

bool Sound::Frequency(int id, int freq)
{
	// this function sets the playback rate (100 - 100.000 Hz)
	if (FAILED(soundFX[id].buffer->SetFrequency(freq)))
		return false;

	// sucessful volume change
	return true;
}

/*********************************************************************************/

bool Sound::Pan(int id, int dir)
{
	// set the pan direction (-10000 to 10000)
	if (FAILED(soundFX[id].buffer->SetPan(dir)))
		return false;

	// sucessful volume change
	return true;
}

/**********************************************************************************/

bool Sound::Playing(int id)
{
	ulong status;
	soundFX[id].buffer->GetStatus(&status);

	return (status & DSBSTATUS_PLAYING);
}
